#include "fileTranslator.h"
#include <ostream>
#include <maya/MFnMesh.h>
#include <maya/MItDag.h>
#include <maya/MDagPath.h>
#include <maya/MFloatArray.h>
#include <maya/MPointArray.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MGlobal.h>
#include <maya/MObjectArray.h>
#include <fstream>
#include <string>
#include <maya/MAnimUtil.h>
#include <maya/MPlug.h>
#include "Tiny Xml\tinyxml.h"
#include <maya/MSelectionList.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MPlugArray.h>
#include <maya/MFnAnimCurve.h>
#include <maya/MTransformationMatrix.h>
#include <maya\MFnIkJoint.h>
#include <maya\MMatrix.h>
#include "Mesh.h"

#define MODEL_SCALE .033f
using namespace std;

const char padName[5] = "_pad";

MStatus FileTranslator::writer(const MFileObject& file, const MString& optionString, FileAccessMode mode)
{	
	// make a dagIterator
	MItDag meshDag(MItDag::kDepthFirst, MFn::kMesh);


	// iterate through dagIterator
	for (; !meshDag.isDone(); meshDag.next())
	{
		//	get current mesh
		MDagPath path;
		meshDag.getPath(path);
		MObject meshNode = meshDag.item();
		if (!meshNode.hasFn(MFn::kMesh))
			continue;
		MFnMesh currMesh(path);
		//	if this mesh is an intermediate object
		if (currMesh.isIntermediateObject())
		//	skip this mesh
			continue;
		//	create writeMesh
		Mesh* writeMesh = new Mesh;
		//	call export mesh on this mesh
		if (!WriteMesh(currMesh, *writeMesh))
			return MStatus::kFailure;
		//	add export mesh to list of meshes to export
		meshesToFile.push_back(writeMesh);
	}
	//////////////////////////////////////////////////////////////////
	// export animation

	// IKEffectors
	// Group nodes
	// Joints
	// blend shapes
	// constraints
	
#if 0
	// create selection list
	MSelectionList list;
	if (mode == kExportActiveAccessMode)
		MGlobal::getActiveSelectionList(list);
	else
	{
		// create a selection list with everything in the world
		MItDag dagit(MItDag::kBreadthFirst);
		// find all top level objects
		for (dagit.next(); !dagit.isDone(); dagit.next())
		{
			MDagPath path;
			if (dagit.getPath(path) != MS::kSuccess)
				continue;

			list.add(path);
			dagit.prune();
		}
	}

	MItDependencyNodes nodeit;
	for (; !nodeit.isDone(); nodeit.next())
	{
		MObject node = nodeit.item();
		// skip dag objects
		if (node.hasFn(MFn::kDagNode))
			continue;
		// only write top level characters
		if (node.hasFn(MFn::kCharacter))
		{
			MFnDependencyNode fnNode(node);
			MObject aMessage = fnNode.attribute(MString("message"));
			MPlug msgPlug(node, aMessage);
			// find connected plug
			MPlugArray srcPlugArray;
			msgPlug.connectedTo(srcPlugArray, false, true);
			// walk through each connection and look for other characters
			unsigned int numPlugs = srcPlugArray.length();
			bool belongsTochar = false;
			for (unsigned int i = 0;(i < numPlugs) && !belongsTochar; i++)
			{
				const MPlug& plug = srcPlugArray[i];
				if (!plug.node().hasFn(MFn::kCharacter))
					continue;
				belongsTochar = true;
			}
			if (!belongsTochar)
				list.add(node);
			continue;
		}
		// filter out unanimated nodes
		if (!MAnimUtil::isAnimated(node))
			continue;
		list.add(node);
	}

	MPlugArray animatedPlugs;
	MAnimUtil::findAnimatedPlugs(list, animatedPlugs);
	unsigned int numPlugs = animatedPlugs.length();
	bool hasTime = false;
	double startTime = 0.0;
	double endTmie = 0.0;
	bool hasUnitless = false;
	double startUnitless = 0.0;
	double endUnitless = 0.0;
	unsigned int i;

	// detirmine the bounds of the animation
	for (i = 0; i < numPlugs; i++)
	{
		MPlug plug = animatedPlugs[i];
		MObjectArray animation;
		// find the animation curve that animates this plug
		if (!MAnimUtil::findAnimation(plug, animation))
			continue;
		MGlobal::displayInfo("Dis niggah got animation!");
		unsigned int numCurves = animation.length();
		for (unsigned int j = 0; j < numCurves; j++)
		{
			MObject animCurveNode = animation[j];
			if (!animCurveNode.hasFn(MFn::kAnimCurve))
				continue;

			MFnAnimCurve animCurve(animCurveNode);
			
			unsigned int numKeys = animCurve.numKeys();
			if (numKeys == 0)
				continue;
			if (animCurve.isUnitlessInput())
			{
				if (!hasUnitless)
				{
					startUnitless = animCurve.unitlessInput(0);
					endUnitless = animCurve.unitlessInput(numKeys - 1);
					hasUnitless = true;
				}
				else
				{
					startUnitless = min(startUnitless, animCurve.unitlessInput(0));
					endUnitless = max(endUnitless, animCurve.unitlessInput(numKeys - 1));
				}
			}

			
		}
	}
#endif


	//MItDag animDag(MItDag::kDepthFirst, MFn::kKeyframeDeltaMove);
	//for (; !animDag.isDone(); animDag.next())
	//{
	//	MGlobal::displayInfo("Dis niggah got bones!");
	//	// get the path to the joint
	//	MDagPath path;
	//	animDag.getPath(path);

	//	// get joint from path
	//	MFnIkJoint joint(path);
	//	MTransformationMatrix m;
	//}

	//MItDag groupNodeDag(MItDag::kDepthFirst);

	//for (; !groupNodeDag.isDone(); groupNodeDag.next())
	//{
	//	MDagPath path;
	//	groupNodeDag.getPath(path);

	//	const char* name = path.fullPathName().asChar();
	//	if (strcmp(name - 5, padName) == 0)
	//	{
	//		MGlobal::displayInfo("Dis niggah got groups!");
	//	}
		// MGlobal::displayInfo(path.fullPathName());
	//}

	//MItDag blendShapes(MItDag::kDepthFirst, MFn::kBlendShape);

	//for (; !groupNodeDag.isDone(); groupNodeDag.next())
	//	MGlobal::displayInfo("Dis niggah got blend shapes!");

	//MItDag ikEffectorsDag(MItDag::kDepthFirst, MFn::kIkEffector);

	//for (; !ikEffectorsDag.isDone(); ikEffectorsDag.next())
	//	MGlobal::displayInfo("Dis niggah got ik effectors!");

	//MItDag ikJointDag(MItDag::kDepthFirst, MFn::kJoint);
	//
	//for (; ikJointDag.isDone(); ikJointDag.next())
	//	MGlobal::displayInfo("Dis niggah got joints!");

#if USE_XML
	FlushFileXML((string(file.path().asChar()) + file.name().asChar()).c_str());
#else
	FlushFileBinary((string(file.path().asChar()) + file.name().asChar()).c_str());
#endif

	// clear meshes
	for (int i = 0; i < meshesToFile.size(); i++)
		delete meshesToFile[i];
	meshesToFile.clear();

	return MStatus::kSuccess;
}

/*static*/ void * FileTranslator::creator(void)
{
	return new FileTranslator;
}

bool FileTranslator::WriteMesh(MFnMesh& currMesh, Mesh& meshToWrite)
{
	// create arrays for normals, uvs, and positions
	MFloatArray Us;
	MFloatArray Vs;
	MPointArray positions;
	MFloatVectorArray normals;
	MFloatVectorArray tangents;
	MMatrix transform = currMesh.transformationMatrix();
	// use currmesh to populate previously created arrays
	currMesh.getNormals(normals);
	currMesh.getPoints(positions);
	currMesh.getTangents(tangents);
	currMesh.getUVs(Us, Vs);
	// if there are no normls or uvs break out of this function and stop saveing the model
	if (normals.length() == 0 || Us.length() == 0 || Vs.length() == 0)
	{
		MGlobal::displayInfo("Error: Model data is invalid!");
		return false;
	}
	// assign meshTowrite its name from curmeshes name
	meshToWrite.name = currMesh.name().asChar();
	// create a MItMeshPolygon iterator and assign it to currmesh.dagpath
	// iterate over MItmeshpolygon...
	for (MItMeshPolygon iter = currMesh.dagPath(); !iter.isDone(); iter.next())
	{
		// the triangle we're going to put into meshToWrite
		Triangle t;
		// get normal, position, and uv index from iterator
		for (int i = 0; i < 3; i++)
		{
			// extract indicies from currmesh
			int positionIndex = iter.vertexIndex(i);
			int normalIndex = iter.normalIndex(i);
			int tangentIndex = iter.tangentIndex(i);
			int uvIndex = 0;
			iter.getUVIndex(i, uvIndex);

			// extract mesh data from curr mesh
			MPoint Mposition = positions[positionIndex];
			MVector Mnormal = normals[normalIndex];
			MVector Mtangent = tangents[tangentIndex];
			float mU = Us[uvIndex];
			float mV = Vs[uvIndex];
			
			// convert data from maya format to game format
			MVector mesh_pos = MVector(Mposition.x, Mposition.y, Mposition.z) * transform;
			Vector3 position = Vector3(mesh_pos.x, mesh_pos.y, mesh_pos.z);
			Vector3 normal((float)Mnormal.x, (float)Mnormal.y, (float)Mnormal.z);
			Vector2 uv(mU, mV);
			Vector3 tangent(Mtangent.x, Mtangent.y, Mtangent.z);

			Vertex v(position, normal, uv, tangent);

			// ensure this vertex is unique
			int Index = -1;
			for (unsigned int ii = 0; ii < meshToWrite.uniqueVertecies.size(); ii++)
			{
				if (v == meshToWrite.uniqueVertecies[ii])
				{
					Index = ii;
					break;
				}
			}

			if (Index != -1)
				t.e[i] = Index;
			else
			{
				meshToWrite.uniqueVertecies.push_back(v);
				t.e[i] = (unsigned int)meshToWrite.uniqueVertecies.size() - 1;
			}
		}
		// add the polygon to the meshToRight
		meshToWrite.indicies.push_back(t);
	}

	return true;
}

void FileTranslator::FlushFileBinary(const char* filename)
{
	vector<Vertex> vertecies;
	vector<unsigned int> indicies;
	unsigned int startIndex = 0;
	for (unsigned int i = 0; i < meshesToFile.size(); i++)
	{
		Mesh* mesh = meshesToFile[i];

		// add mesh vertecies
		for (unsigned int ii = 0; ii < mesh->uniqueVertecies.size(); ii++)
			vertecies.push_back(mesh->uniqueVertecies[ii]);
		// add mesh indicies
		for (unsigned int ii = 0; ii < mesh->indicies.size(); ii++)
		{
			indicies.push_back(startIndex + mesh->indicies[ii].A);
			indicies.push_back(startIndex + mesh->indicies[ii].B);
			indicies.push_back(startIndex + mesh->indicies[ii].C);
		}
		startIndex += unsigned int(mesh->indicies.size() * 3u);
	}

	fstream file(filename, ios::out | ios::binary);
	if (file.is_open())
	{
		// write number of vertecies
		unsigned int vertexCount = vertecies.size();
		file.write(reinterpret_cast<char*>(&vertexCount), sizeof(unsigned int));
		// write vertecies
		file.write(reinterpret_cast<char*>(&vertecies[0]), vertecies.size() * sizeof(Vertex));
		// write number of indicies
		unsigned int indexCount = indicies.size();
		file.write(reinterpret_cast<char*>(&indexCount), sizeof(unsigned int));
		// write indicies
		file.write(reinterpret_cast<char*>(&indicies[0]), indicies.size() * sizeof(unsigned int));
		// close the file
		file.close();
	}
	else
		MGlobal::displayError(MString("Failed to save file"));
}

void FileTranslator::FlushFileXML(const char* filename)
{
	fstream file(filename, ios::out);
	if (file.is_open())
	{
		file << meshesToFile.size();
		for (unsigned int i = 0; i < meshesToFile.size(); i++)
		{
			file << "VertexBuffer: ";
			file << "size = " << meshesToFile[i]->uniqueVertecies.size() << "\n";
			for (unsigned int ii = 0; ii < meshesToFile[i]->uniqueVertecies.size(); ii++)
			{
				Vertex v = meshesToFile[i]->uniqueVertecies[ii];
				file << "{" << v.position.x << ", " << v.position.y << ", " << v.position.z << ", }"
					<< "\n{ " << v.normal.x << ", " << v.normal.y << ", " << v.normal.y << "} "
					<< "\n{ " << v.texCoord.x << ", " << v.texCoord.y << " }\n";
			}

			file << "IndexBuffer: ";
			file << "size = " << meshesToFile[i]->uniqueVertecies.size() << "\n";
			for (unsigned int ii = 0; ii < meshesToFile[i]->indicies.size(); ii++)
			{
				Triangle t = meshesToFile[i]->indicies[ii];
				file << "{ " << t.A << ", " << t.B << ", " << t.C << "}\n";
			}
		}
		file.close();
	}
}